package com.hwq.sql.script.utils;


import com.hwq.sql.script.exception.ServerException;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class BeanMapUtil {

    /**
     * 将 Bean 转化为 Map
     * @param t  Bean
     * @return   Map
     */
    public static <T> Map<String, Object> beanTpMap(T t) {
        if (t == null) return null;
        Field[] fields = t.getClass().getDeclaredFields();
        Map<String, Object> map = new HashMap<>();
        try {
            for (Field field : fields) {
                field.setAccessible(true);
                map.put(field.getName(), field.get(t));
            }
            return map;
        } catch (IllegalAccessException ex) {
            throw new ServerException(ex);
        }
    }

    /**
     * 将 List(Bean) 转化为 List(Map)
     * @param ts  List(Bean)
     * @return    List(Map)
     */
    public static <T> List<Map<String, Object>> beanToMap(List<T> ts) {
        List<Map<String, Object>> list = new ArrayList<>();
        if (CollectionUtils.isEmpty(ts)) return list;
        Class<?> tClass = ts.get(0).getClass();
        Field[] fields = tClass.getDeclaredFields();
        for (Field field : fields) field.setAccessible(true);
        try {
            for (T t: ts) {
                Map<String, Object> map = new HashMap<>();
                for (Field field : fields) {
                    map.put(field.getName(), field.get(t));
                }
                list.add(map);
            }
        } catch (IllegalAccessException ex) {
            throw new ServerException(ex);
        }
        return list;
    }

    /**
     * 将 Map 转化为 Bean
     * @param map    Map
     * @param tClass Bean 的字节码对象
     * @return       Bean
     */
    public static <T> T mapToBean(Map<String, Object> map, Class<T> tClass)  {
        Field[] fields = tClass.getDeclaredFields();
        try {
            T t = tClass.newInstance();
            for (Field field : fields) {
                field.setAccessible(true);
                field.set(t, map.get(field.getName()));
            }
            return t;
        } catch (IllegalAccessException | InstantiationException ex) {
            throw new ServerException(ex);
        }
    }

    /**
     * 将 List(Map) 转化为 List(Bean)
     * @param maps   List(Map)
     * @param tClass Bean 的字节码对象
     * @return       List(Bean)
     */
    public static <T> List<T> mapToBean(List<Map<String, Object>> maps, Class<T> tClass) {
        List<T> ts = new ArrayList<>();
        if (CollectionUtils.isEmpty(maps)) return ts;
        Field[] fields = tClass.getDeclaredFields();
        for (Field field : fields) field.setAccessible(true);
        try {
            for (Map<String, Object> map : maps) {
                T t = tClass.newInstance();
                for (Field field : fields) {
                    field.set(t, map.get(field.getName()));
                }
                ts.add(t);
            }
            return ts;
        } catch (IllegalAccessException | InstantiationException ex) {
            throw new ServerException(ex);
        }
    }

    /**
     * 将 Bean 转化为 新 Bean，签名和类型都相同的字段 才会赋值给新的 Bean
     * @param obj     原 Bean
     * @param tClassB 新 Bean 的字节码对象
     * @return        新 Bean
     */
    public static <T> T beanToBean(Object obj, Class<T> tClassB) {
        if (obj == null) return null;
        Class<?> tClassA = obj.getClass();
        List<Field> listA = new ArrayList<>();
        List<Field> listB = new ArrayList<>();
        setEqualFiels(listA, listB, tClassA, tClassB);
        try {
            T t = tClassB.newInstance();
            for (int i = 0; i < listA.size(); i++) {
                Object value = listA.get(i).get(obj);
                listB.get(i).set(t, value);
            }
            return t;
        } catch (Exception ex) {
            throw new ServerException(ex);
        }
    }

    /**
     * 将 List(Bean) 转化为 另一个类 List(Bean)，签名和类型都相同的字段 才会赋值给新的 Bean
     * @param ts      原 List(Bean)
     * @param tClassB 新 Bean 的字节码对象
     * @return        新 List(Bean)
     */
    public static <T> List<T> beanToBean(List<?> ts, Class<T> tClassB) {
        List<T> list = new ArrayList<>();
        if (CollectionUtils.isEmpty(ts)) return list;
        Class<?> tClassA = ts.get(0).getClass();
        List<Field> listA = new ArrayList<>();
        List<Field> listB = new ArrayList<>();
        setEqualFiels(listA, listB, tClassA, tClassB);
        try {
            for (Object object : ts) {
                T t = tClassB.newInstance();
                for (int i = 0; i < listA.size(); i++) {
                    Object value = listA.get(i).get(object);
                    listB.get(i).set(t, value);
                }
                list.add(t);
            }
            return list;
        } catch (Exception ex) {
            throw new ServerException(ex);
        }
    }

    /**
     * 获取两个实体类中相同的字段对象，要求属性的 签名 和 类型 同时一致
     * @param listA   存放 A类相同 字段的列表
     * @param listB   存放 B类相同 字段的列表
     * @param tClassA A类的字节码对象
     * @param tClassB B类的字节码对象
     */
    private static void setEqualFiels(List<Field> listA, List<Field> listB, Class<?> tClassA, Class<?> tClassB) {
        Field[] fieldA = tClassA.getDeclaredFields();
        Field[] fieldB = tClassB.getDeclaredFields();
        for (Field field : fieldA) field.setAccessible(true);
        for (Field field : fieldB) field.setAccessible(true);
        for (Field fA : fieldA) {
            for (Field fB : fieldB) {
                if (fA.getName().equals(fB.getName()) && fA.getType().equals(fB.getType())) {
                    listA.add(fA);
                    listB.add(fB);
                    break;
                }
            }
        }
        if (listA.size() == 0) {
            String str = tClassA.getName() + " 与 " + tClassB.getName() + " 无相同字段";
            throw new ServerException(str);
        }
    }

    /**
     * 将 实体类的 普通数据列表 转化为 树状结构的数据列表
     * @param ts    平行结构的数据列表
     * @param id    关联主键属性名
     * @param pid   关联父主键属性名
     * @param value 第一级数据的值
     * @param child 下一级存放的容器
     * @return 树状结构的数据列表
     */
    public static <T> List<Map<String, Object>> beanToTree(
            List<T> ts, String id, String pid, String value, String child
    ) {
        List<Map<String, Object>> list = beanToMap(ts);
        return mapToTree(list, id, pid, value, child);
    }

    /**
     * 将 Map 集合的普通数据列表转化为 树状结构的数据列表
     * @param ts    平行结构的数据列表
     * @param id    关联主键属性名
     * @param pid   关联父主键属性名
     * @param value 第一级数据的值
     * @param child 下一级存放的容器
     * @return 树状结构的数据列表
     */
    @SuppressWarnings("unchecked")
    public static List<Map<String, Object>> mapToTree(
            List<Map<String, Object>> ts, String id, String pid, String value, String child
    ) {
        List<Map<String, Object>> resultTs = new ArrayList<>();
        List<Map<String, Object>> remanent = new ArrayList<>();
        List<Map<String, Object>> tempTs = new ArrayList<>();
        List<String> pids = new ArrayList<>();
        for (Map<String, Object> map : ts) {
            if (map.get(pid).toString().equals(value)) {
                resultTs.add(map);
                tempTs.add(map);
                pids.add(map.get(id).toString());
            } else {
                remanent.add(map);
            }
        }
        if (resultTs.size() == 0) return ts;
        while (remanent.size() > 0) {
            List<Map<String, Object>> tempRemanent = new ArrayList<>();
            List<Map<String, Object>> tempTempTs = new ArrayList<>();
            List<String> tempPids = new ArrayList<String>();
            for (Map<String, Object> map : remanent) {
                String pidValue = map.get(pid).toString();
                int index = pids.indexOf(pidValue);
                if (index == -1) {
                    tempRemanent.add(map);
                } else {
                    Map<String, Object> sMap = tempTs.get(index);
                    List<Map<String, Object>> children = (List<Map<String, Object>>) sMap.get(child);
                    if (children == null) {
                        children = new ArrayList<>();
                        sMap.put(child, children);
                    }
                    children.add(map);
                    tempTempTs.add(map);
                    tempPids.add(map.get(id).toString());
                }
            }
            if (tempTs.size() == 0) {
                resultTs.addAll(remanent);
                break;
            }
            remanent = tempRemanent;
            pids = tempPids;
            tempTs = tempTempTs;
        }
        return resultTs;
    }
}
